#include "sys.h"
#include "log.h"

#define debug(fmt, arg...) fprintf(stderr, fmt "\n", ##arg)
#define PARSE_CHAR(c) ((c) < 0x21 ? '.' : (c) > 126 ? '.' : (c))

log_t Log = {0};

static int get_timestrap_us(char* tmstrap)
{
    struct tm *p, tm_p;
    struct timeval tv;
    time_t timep;

    gettimeofday(&tv, NULL);
    time(&timep);
    localtime_r(&timep, &tm_p);
    p = &tm_p;
    return sprintf(tmstrap, "%d-%02d-%02d_%02d:%02d:%02d.%06ld",
        1900 + p->tm_year, 1 + p->tm_mon, p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec, tv.tv_usec);
}

static int get_timestrap(char* tmstrap)
{
    struct tm *p, tm_p;
    time_t timep;

    time(&timep);
    localtime_r(&timep, &tm_p);
    p = &tm_p;
    return sprintf(tmstrap, "%d-%02d-%02d_%02d:%02d:%02d",
        1900 + p->tm_year, 1 + p->tm_mon, p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec);
}

static int str2level(char* str)
{
    if (!strcasecmp(str, "trace"))
        return LOG_TRACE;
    if (!strcasecmp(str, "debug"))
        return LOG_DEBUG;
    if (!strcasecmp(str, "info"))
        return LOG_INFO;
    if (!strcasecmp(str, "notice"))
        return LOG_NOTICE;
    if (!strcasecmp(str, "warn"))
        return LOG_WARN;
    if (!strcasecmp(str, "error"))
        return LOG_ERROR;
    if (!strcasecmp(str, "fatal"))
        return LOG_FATAL;

    return LOG_FATAL;
}
static void check_log_size(log_t* log)
{
    struct stat st;
    char new_filename[128] = { 0 };

    if (log->fp != stdout && log->log_size != 0) {
        stat(log->file_name, &st);
        if (st.st_size > log->log_size) {
            if (log->mode & LOG_MODE_ROLLBACK) {
                log_finish(log);
                sprintf(new_filename, "%s.%s", log->timestamp, log->file_name);
                rename(log->file_name, new_filename);
                log_open(log);
            } else {
                freopen(log->file_name, "w", log->fp);
            }
        }
    }
}
int log_write(log_t* log, int level, char* fmt, ...)
{
    int len = 0;
    char sprint_buf[1024 * 4] = { 0 };
    va_list args;

    if(log->fp == NULL){
        return 0;
    }

    check_log_size(log);

    if (level <= log->level) {
        if (level != LOG_OUTPUT) {
            len = get_timestrap(sprint_buf);
        }
        va_start(args, fmt);
        vsnprintf(sprint_buf + len, sizeof(sprint_buf), fmt, args);
        va_end(args);
        if (0 > fputs(sprint_buf, log->fp)) {
            return 0;
        }
        if (log->level >= LOG_DEBUG)
            fflush(log->fp);
    }

    return 1;
}

int log_buffer(log_t* log, int level, unsigned char* buf, int buf_len)
{
    int i = 0;

    check_log_size(log);

    if (level <= log->level) {
        for (i = 0; i < buf_len; i++) {
            fprintf(log->fp, "0x%2x(%c) ", buf[i], PARSE_CHAR(buf[i]));
            fflush(log->fp);
            if ((i + 1) % 16 == 0)
                fprintf(log->fp, "\n");
        }
        if (i % 16)
            fprintf(log->fp, "\n");
        fprintf(log->fp, "^^^^^^^^^^^^^buf len:%d^^^^^^^^^^^^^\n", buf_len);
    }
    return 1;
}
static int log_opt(log_t* log, int argc, char** argv)
{
    int log_mode_append = 0, log_rollback = 0;
    OPT_T opt[] = {
        { "--log-level", log->log_level, OPT_STRING, "set log level(trace, debug, info, notice, output)." },
        { "--log-size", &log->log_size, OPT_INT, "set log file max size (Bytes)." },
        { "--log-file", log->file_name, OPT_STRING, "set output file." },
        { "--log-buf", &log->buf_size, OPT_INT, "set log buffer size (Bytes)." },
        { "--log-append", &log_mode_append, OPT_NONE, "append to log file." },
        { "--log-rollback", &log_rollback, OPT_NONE, "append to log file." },
    };
    if (0 > opt_parse(opt, sizeof(opt), argc, argv)) {
        return 1;
    }
    log->level = str2level(log->log_level);
    if (!strcmp("stdout", log->file_name)) //output to stdout
    {
        log->fp = stdout;
        return 1;
    }
    if (log_mode_append) {
        log->mode |= LOG_MODE_APPEND;
    }
    if (log_rollback) {
        log->mode |= LOG_MODE_ROLLBACK;
    }

    return 1;
}
int log_set_level(log_t* log, char* level)
{
    log->level = str2level(level);
    return 1;
}
void log_set_mode(log_t* log, int mode)
{
    log->mode = mode;
}
static int log_default(log_t* log)
{
    log->level = LOG_NOTICE;
    strcpy(log->log_level, "notice");
    log->fp = stdout;
    strcpy(log->file_name, "stdout");
    log->log_size = 1024 * 10;
    log->buf_size = 0;
    log->mode = 0;
    return 1;
}
static void log_env(log_t* log)
{
    char* p = NULL;

    p = getenv("LOG_LEVEL");
    if (p)
        strcpy(log->log_level, p);

    p = getenv("LOG_FILE");
    if (p)
        strcpy(log->file_name, p);

    p = getenv("LOG_SIZE");
    if (p)
        log->log_size = atoi(p);
}
int log_initv(int argc, char** argv)
{
    log_default(&Log);
    log_env(&Log);
    log_opt(&Log, argc, argv);
    log_open(&Log);
    return 1;
}
log_t* log_new(char* output, int log_size, char* level)
{
    log_t* log = malloc(sizeof(log_t));
    if (!log) {
        fprintf(stderr, "log malloc error.");
        return NULL;
    }
    bzero(log, sizeof(log_t));
    log_default(log);
    log_env(log);

    strcpy(log->file_name, output);
    log->log_size = log_size;
    log->level = str2level(level);
    return log;
}
int log_init(char* output, int log_size, char* level)
{
    bzero(&Log, sizeof(Log));
    log_default(&Log);

    strcpy(Log.file_name, output);
    Log.log_size = log_size;
    Log.level = str2level(level);

    log_env(&Log);
    return 1;
}
int log_open(log_t* log)
{
    if (!strcmp(log->file_name, "stdout") || !strcmp(log->file_name, "stderr")) {
        log->fp = stdout;
        return 1;
    }

    if (log->mode & LOG_MODE_APPEND) {
        log->fp = fopen(log->file_name, "a");
    } else {
        log->fp = fopen(log->file_name, "w");
    }
    if (!log->fp) {
        fprintf(stderr, "log open error.");
        return -1;
    }
    if (log->buf_size) {
        log->buffer = malloc(log->buf_size);
        setbuffer(log->fp, log->buffer, log->buf_size);
    }
    get_timestrap_us(log->timestamp);
    return 1;
}
void log_finish(log_t* log)
{
    if (log->fp && log->fp != stdout) {
        fclose(log->fp);
        log->fp = NULL;
    }
}